/*  RingBuffer
 *  For non-blocking IO. Inspired by cloudwu/mread
 *  Copyright (C) 2016 ZHANG X. <201560039@uibe.edu.cn>
 *   __ |  \   /   __| 
 *     /     /    |    
 *   ___|  _/ _\ \___| 
 *
 *  **All Rights Reserved**
 */
#include "ringbuffer.h"
#include <string.h>
#define SEGSTSZ sizeof(struct Ring_segment)
#define SEG_NEXT(r, seg) (RING_SEG_PTR((r), (seg)->next_sg))
#define SEG_OFFSET(r, seg) ((char *)(seg)-(char *)(r)->buffer)

uint32_t
ring_drop_conn(struct Ring *r, uint32_t *conn_head, uint32_t max_len){
    register struct Ring_segment *curr = RING_SEG_PTR(r, *conn_head);
    /*  Already marked inactive */
    if(curr->connection == INACTIVE_CONN) return 0;
    /*  First segment   */
    uint32_t droped_length = curr->length;
    /*  Remember droped conn for close()    */
    RING_SEG_PTR(r, r->tail)->connection = curr->connection;
    if(droped_length > max_len) goto slice_segment;
    while((curr->connection = INACTIVE_CONN)
            && curr->conn_next != NO_SUCCEEDING
            && (droped_length += RING_SEG_LEN(r,
                    curr->conn_next)) <= max_len)
        curr = RING_SEG_PTR(r, curr->conn_next);
    if(droped_length > max_len){
slice_segment:
        /*   Move remained data to front. */
        if(curr->conn_next == INACTIVE_CONN){
            *conn_head = curr->conn_next;
            curr = RING_SEG_PTR(r, curr->conn_next);
        }
        memmove((void*)(curr+1),
                (char*)(curr+1) + curr->length
                - (droped_length - max_len),
                droped_length - max_len);
        curr->length = droped_length - max_len;
        return max_len;
    }
    *conn_head = curr->conn_next;
    return droped_length;
}
void
ring_head_resize(struct Ring *r){
    struct Ring_segment *tailptr = RING_SEG_PTR(r, r->tail);
    register uint32_t curr = r->tail;
    /*  Merge spaces of tail with those inactive segments. */
    uint32_t merged_tail = r->size;
    /*  A segment should not exceed the boundary    */
    while((curr = RING_SEG_PTR(r, curr)->next_sg) > r->tail
            && (merged_tail = curr)
            && RING_SEG_PTR(r, curr)->connection
            == INACTIVE_CONN
         );
    tailptr->length = merged_tail - r->tail - SEGSTSZ;
    /*  Link to head segment, the first one in use.   */
    tailptr->next_sg = curr;
}

uint32_t
ring_rearrange(struct Ring *r, size_t size){
    /*  Called after read() or recv() wrote.  */
    uint32_t recv_head = r->tail;
    /*  Return tail if no writing.  */
    if(size<=0)return recv_head;
    struct Ring_segment *new_segment = RING_SEG_PTR(r, recv_head);
    struct Ring_segment *new_tail = new_segment;
#ifdef HAVE_RING_WRITE_EXCEED
    if(size > RING_SEG_PTR(r->tail)->length){
        /*  Write exceeded, should not happen.  */
        if(r->tail + size + SEGSTSZ
                <= RING_SEG_PTR(r, r->tail)->conn_next){
            /*  Preserve connection's next segment   */
            new_segment->next_sg = new_segment->conn_next;
            new_segment->length = new_segment->next_sg
                - recv_head - SEGSTSZ;
        }else{
            /*  Discard all */
            new_segment->next_sg = recv_head;
            new_segment->length = r->size
                - recv_head - SEGSTSZ;
        }
    }
#endif /* HAVE_RING_WRITE_EXCEED */
    /*  Near the boundary.  */
    if(size + SEGSTSZ >= new_tail->length)
        r->tail = new_tail->next_sg;
    else
        r->tail += SEGSTSZ + size;
    new_tail = RING_SEG_PTR(r, r->tail);
    /*  If tail reached head, or near the boundary.
     *  If so, new_tail->connection is the connection index
     *  which is now marked inactive.
     */
    if(new_segment->next_sg == r->tail)
        ring_drop_conn(r, &(new_segment->next_sg), NO_SUCCEEDING);
    else{
        new_tail->next_sg = new_segment->next_sg;
        new_tail->length = new_segment->length - size - SEGSTSZ;
        new_tail->connection = INACTIVE_CONN;
    }
    /*  Next segment of tail's connection
     *  is the last segment received. */
    new_tail->conn_next = recv_head;
    new_segment->next_sg = r->tail;
    new_segment->length = size;
    ring_head_resize(r);
    return recv_head;
}
void
ring_link_conn(struct Ring *r, uint32_t conn_head, uint32_t segment){
    if(conn_head == segment){
        RING_SEG_PTR(r, segment)->conn_next = NO_SUCCEEDING;
        return;
    }
    while(RING_SEG_PTR(r, conn_head)->conn_next != NO_SUCCEEDING)
        conn_head = RING_SEG_PTR(r, conn_head)->conn_next;
    RING_SEG_PTR(r, conn_head)->conn_next = segment;
    RING_SEG_PTR(r, segment)->conn_next = NO_SUCCEEDING;
    RING_SEG_PTR(r, segment)->connection = RING_SEG_PTR(r, conn_head)->connection;
}

uint32_t
ring_collect(struct Ring *r, uint32_t head, uint32_t length){
    register struct Ring_segment *curr = RING_SEG_PTR(r, head);
    /*  Been overwrote 
    if(curr->length + SEGSTSZ
            != (curr->next_sg==0 ?
            r->size : curr->next_sg) - head){
        head = INACTIVE_CONN;
        return NULL;
    }
    */
    /*  Supply one segment  */
    if(curr->length >= length)
        return head;
    /*  Insufficient    */
    if(curr->conn_next == NO_SUCCEEDING)
        return head;
    /*  Collect segments    */
    char *tail = RING_TAIL_BUFFER(r);
    uint32_t length_collected = 0;
    uint32_t connection = curr->connection;
    uint32_t tail_size = RING_TAIL_SIZE(r);
    do{
        memcpy(tail + length_collected, (void*)(curr+1), curr->length); 
        length_collected += curr->length;  
        /*  Delete copied.  */
        curr->connection = INACTIVE_CONN;
    }while(curr->conn_next != NO_SUCCEEDING
            && length_collected < length
            && (length_collected + RING_SEG_LEN(r, curr->conn_next) <= tail_size)
            && (curr = RING_SEG_PTR(r, curr->conn_next)));
    /*  Head index has been moved due to collecting.  */
    head = ring_rearrange(r, length_collected);
    RING_SEG_PTR(r, head)->connection = connection;
    RING_SEG_PTR(r, head)->conn_next = curr->conn_next;
    return head;
}
uint32_t ring_next_conn(struct Ring *r, uint32_t segment){
    uint32_t curr_seg = segment;
    uint32_t curr = RING_SEG_PTR(r, segment)->connection;
    uint32_t another = INACTIVE_CONN;
    /*  Find another connection.    */
    do{
        curr_seg = RING_SEG_PTR(r, curr_seg)->next_sg;
        another = RING_SEG_PTR(r, curr_seg)->connection;
    }while(another != curr
            && another != INACTIVE_CONN
            && curr_seg != segment);
    return another;
}
